package com.redis.base.multiple;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redis.base.redis.IRedis;
import com.redis.base.utils.JsonUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class SingleRedis extends AbstractRedis implements IRedis {

	private static final Logger log = LoggerFactory.getLogger(SingleRedis.class);

	private JedisPool jedisPool = null;

	private RedisConfig redisConfig;

	public SingleRedis(RedisConfig redisConfig) {
		this.redisConfig = redisConfig;
		getJedisPool();
	}
	
	
	protected JedisPoolConfig getJedisPoolConfig(RedisConfig rediconfig) {
		 JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(rediconfig.getMaxIdle());
        config.setTestOnBorrow(rediconfig.isTestOnBorrow());
        config.setMaxTotal(rediconfig.getMaxTotal());
        config.setMaxWaitMillis(rediconfig.getMaxWaitMillis());
        config.setMinIdle(rediconfig.getMinIdle());//设置最小空闲数
        config.setTestOnBorrow(rediconfig.isTestOnBorrow());
        config.setTestOnReturn(rediconfig.isTestOnReturn());
        //Idle时进行连接扫描
        config.setTestWhileIdle(rediconfig.isTestWhileIdle());
        //表示idle object evitor两次扫描之间要sleep的毫秒数
        config.setTimeBetweenEvictionRunsMillis(rediconfig.getTimeBetweenEvictionRunsMillis());
        //表示idle object evitor每次扫描的最多的对象数
        config.setNumTestsPerEvictionRun(rediconfig.getNumTestsPerEvictionRun());
        //表示一个对象至少停留在idle状态的最短时间，然后才能被idle object evitor扫描并驱逐；这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义
        config.setMinEvictableIdleTimeMillis(rediconfig.getMinEvictableIdleTimeMillis());
        
        return config;
	}

	public void getJedisPool() {
		jedisPool = new JedisPool(getJedisPoolConfig(redisConfig), redisConfig.getHostName(), redisConfig.getPort(),
				redisConfig.getTimeout(), redisConfig.getPassword());
	}


	/**
	 * 获取Jedis实例
	 * 
	 * @return
	 */
	public synchronized  Jedis getJedis() {
		try {
			if (!redisConfig.isEnable()) {
				return null;
			}
			
			Jedis jedis = null;
			if (jedisPool != null) {
				jedis = jedisPool.getResource();
				jedis.select(redisConfig.getDatabase());
				return jedis;
			} else {
				return null;
			}
		} catch (Exception e) {
			log.error(e.getMessage());
			return null;
		}
	}

	/**
	 * 存数据到redis
	 * 
	 * @param key
	 * @param value
	 */
	@Override
	public void put(final String key, final String value) {
		put(key, value, redisConfig.getExpire());
	}

	@Override
	public void put(final String key, final String value, final Long expire) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is put, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return;
			}
			if (log.isDebugEnabled()) {
				log.debug("put redis====:" + key + ":" + value);
			}
			jedis.set(key, value);
			expire(jedis,key, redisConfig.getExpire());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public String get(final String key) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return null;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return "";
			}
			return (String) jedis.get(key);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return null;
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public <T> T get(final String key, Class<T> objClass) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return null;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return null;
			}
			return JsonUtil.json2Bean(jedis.get(key), objClass);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return null;
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public <T> List<T> getList(final String key, Class<T> objClass) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return null;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is get, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return null;
			}
			return JsonUtil.json2List(String.valueOf(jedis.get(key)), objClass);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return null;
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public void put(final String key, final Object value) {
		put(key, value, redisConfig.getExpire());
	}

	@Override
	public void put(final String key, final Object value, final Long expire) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is putObj, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return;
			}
			jedis.set(key, JsonUtil.bean2Json(value));
			expire(jedis,key, redisConfig.getExpire());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public void unLock(final String... keys) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is release, key [" + keys + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return;
			}
			for (String key : keys) {
				String currentValueStr = String.valueOf(jedis.get(getLockKey(key)));
			    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
			    	jedis.del(getLockKey(key));
			    }
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}finally {
			returnResource(jedis);
		}
	}

	/**
	 * 
	 * @Title: deleteSub
	 * 
	 * @return
	 * @return: Object
	 */
	@Override
	public Object deleteLike(final String... keys) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return null;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug(
						"redis: method is deleteLike, key [" + keys + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return 0;
			}
			List<String> keyList = new ArrayList<String>();
			Object result = null;
			if (keys == null || keys.length <= 0) {
				return null;
			}
			for (Object key : keys) {
				Set<byte[]> keysTemp = jedis.keys(("*" + String.valueOf(key) + "*").getBytes());
				for (byte[] by : keysTemp) {
					result = jedis.del(by);
					keyList.add("key:" + result + ",");
				}
			}
			return result;

		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return null;
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public boolean delete(final String key) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return false;
		}
		try {
			if (log.isDebugEnabled()) {
				log.debug("redis: method is delete, key [" + key + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return false;
			}
			jedis.del(key);
			return true;
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return false;
		}finally {
			returnResource(jedis);
		}
	}

	@Override
	public boolean tryLock(String lockKey) {
		return lock(lockKey, redisConfig.isSpin(), getSpinNum(redisConfig.getSpinNum(), DEFAULT_SPIN_NUM));
	}

	@Override
	public boolean tryLock(String lockKey, boolean isSpin) {
		return lock(lockKey, isSpin, getSpinNum(redisConfig.getSpinNum(), DEFAULT_SPIN_NUM));
	}

	@Override
	public boolean tryLock(String lockKey, boolean isSpin, int spinNum) {
		return lock(lockKey, isSpin, getSpinNum(redisConfig.getSpinNum(), DEFAULT_SPIN_NUM));
	}

	@Override
	public boolean lock(final String lockKey, final boolean isSpin, final int spinNum) {
		
		try {
			if (log.isDebugEnabled()) {
				log.debug(
						"redis: method is acquire, key [" + lockKey + "],cache_redis [" + redisConfig.isEnable() + "]");
			}
			if (!redisConfig.isEnable()) {
				return true;
			}
			if (log.isDebugEnabled()) {
				log.debug("Start acquiring a lock  {},thread {}.", lockKey, Thread.currentThread().getId());
			}
			final String redisLockKey = getLockKey(lockKey);
			synchronized (redisLockKey) {
				long spinNumTemp = getSpinNum(redisConfig.getSpinNum(), DEFAULT_SPIN_NUM);
				while (spinNumTemp > 0) {
					if (log.isDebugEnabled()) {
						log.debug("start timeout:{},key:{} ", System.currentTimeMillis(), redisLockKey);
					}
					Jedis jedis = getJedis();
					if(jedis == null) {
						log.info("{} jedis is empty.",redisLockKey);
						return true;
					}
					try {
						String expireTime = String.valueOf(System.currentTimeMillis() + redisConfig.getLockExpire());
						Long value = jedis.setnx(redisLockKey, expireTime);
						if (value >= 1) {
							if (log.isDebugEnabled()) {
								log.debug(" Manage to get a redis lock {},lockKey {} ", System.currentTimeMillis(),
										redisLockKey);
							}
							return true;
						} 
						if (log.isDebugEnabled()) {
							log.debug("lock timeout {},key {},thrread {} ", System.currentTimeMillis(), redisLockKey,
									Thread.currentThread().getId());
						}
						String currentValueStr = jedis.get(redisLockKey);
					    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
					        // 锁已过期，获取上一个锁的过期时间，并设置现在锁的过期时间
					        String oldValueStr = jedis.getSet(redisLockKey, expireTime);
					        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
					            // 考虑多线程并发的情况，只有一个线程的设置值和当前值相同，它才有权利加锁
					            return true;
					        }
					    }
						if (!isSpin) {// 如果不需要自旋获取锁，直接返回结果
							return false;
						}
						
					}finally {
						returnResource(jedis);
					}
	
					spinNumTemp -= 1;
					Thread.sleep(redisConfig.getSpinThreadTime());
				}
			}
			log.info("stop timeout:{},key:{},thread:{} ", System.currentTimeMillis(), lockKey,
					Thread.currentThread().getId());
			log.info("Failed to get the lock {},thread {} ", lockKey, Thread.currentThread().getId());
			return false;

		} catch (Exception e) {
			log.error(e.getMessage(), e);
			return true;
		}
	}

	@SuppressWarnings("deprecation")
	public void expire(Jedis jedis,String key, Long milliseconds) {
		jedis.pexpire(key, milliseconds);
	}
	
	   /**
     * 释放jedis资源
     * @param jedis
     */
    @SuppressWarnings("deprecation")
	public synchronized  void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.close();
        }
    }

	@Override
	public long incr(String key) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return 0;
		}
		try {
			return jedis.incr(key);
		} catch (Exception e) {
			log.error(e.getMessage());
		}finally {
			returnResource(jedis);
		}
		return 0;
	}


	@Override
	public Set<String> keys(String key) {
		Jedis jedis = getJedis();
		if(jedis == null) {
			return null;
		}
		try {
			return jedis.hkeys(key);
		}finally {
			returnResource(jedis);
		}
	}

}
